using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using Granite.Collections;

namespace Granite.Eventing
{
	/// <summary>
	/// This is used to attach weak event handlers to the indicated source. 
	/// </summary>
	/// <typeparam name="T">The type of event being listened to.</typeparam>
	public abstract class EventManager<T> where T : EventArgs
	{
		readonly WeakReferenceCollection<IListener<T>> m_Targets = new WeakReferenceCollection<IListener<T>>();
		bool m_Listening;

		/// <summary>
		/// The implementation of this event must attach the event source to the EventFired method.
		/// </summary>
		protected abstract void AttachToEvent();


		/// <summary>
		/// The implementation of this event must deattach the event source to the EventFired method.
		/// </summary>
		protected abstract void DetachFromEvent();


		/// <summary>
		/// This dispatches the event to all of the listeners
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void EventFired(object sender, T e)
		{
			m_Targets.CleanUp();

			//disconnect if no longer needed
			if (m_Targets.Count == 0)
			{
				m_Listening = false;
				DetachFromEvent();
			}
			else
			{
				foreach (var target in m_Targets)
				{
					Contract.Assume(target != null);
					target.Invoke(sender, e);
				}
			}

		}

		/// <summary>
		/// Adds a weak event handler.
		/// </summary>
		/// <param name="eventHandler"></param>
		public void AddHandler(IListener<T> eventHandler)
		{
			if (eventHandler == null)
				throw new ArgumentNullException("eventHandler", "eventHandler is null.");
			Contract.EndContractBlock();

			m_Targets.Add(eventHandler);
			if (!m_Listening)
			{
				AttachToEvent();
				m_Listening = true;
			}
		}

		/// <summary>
		/// Removes a weak event handler.
		/// </summary>
		/// <param name="eventHandler"></param>
		public void RemoveHandler(IListener<T> eventHandler)
		{
			if (eventHandler == null)
				throw new ArgumentNullException("eventHandler", "eventHandler is null.");
			Contract.EndContractBlock();

			m_Targets.Remove(eventHandler);
			m_Targets.CleanUp();

			if (m_Listening && m_Targets.Count == 0)
			{
				m_Listening = false;
				DetachFromEvent();
			}
		}

		[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic"), SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
		[ContractInvariantMethod]
		void ObjectInvariant()
		{
			Contract.Invariant(m_Targets != null);
		}
	}
}
